/* LINUXIO.C
 * Copyright (c) 2007 MissingNODE 
 * Based on GPL OpenQM Code
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * 
 * 
 * START-HISTORY:
 * Oct-10-2007 Initial Revision
 * END-HISTORY
 *
 * START-DESCRIPTION:
 *
 */

#include <tm.h>
#include <tio.h>
#include <header.h>
#include <config.h>
#include <telnet.h>
#include <tmtermlb.h>
#include <err.h>
#include <tmnet.h>

#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <signal.h>
#include <time.h>
#include <pwd.h>

#include <sched.h>

public int ChildPipe;

#define RING_SIZE 1024
private volatile u_char ring_buff[RING_SIZE];
private volatile short int ring_in = 0;
private volatile short int ring_out = 0;

#ifdef ASYNCIO
   private void io_handler(int sig);
#endif
private void do_input(void);
private bool input_handler_enabled = TRUE;
private bool piped_input = FALSE;

/* Keyboard */

private int ttyin = 0;
private struct termios old_tty_settings;
private struct termios new_tty_settings;
private bool tty_modes_saved = FALSE;
private short int type_ahead = -1;

private void signal_handler(int signum);

void set_term(bool trap_break);
void set_old_tty_modes(void);
void set_new_tty_modes(void);
bool negotiate_telnet_parameter(void);

/* ======================================================================
   start_connection()  -  Start Linux socket / pipe based connection      */

bool start_connection(int unused)
{
 socklen_t n;
 struct sockaddr_in sa;
 int flag;


 if (is_TMVbSrvr) strcpy(command_processor, "$VBSRVR");

 if (connection_type == CN_SOCKET)
  {
   if (is_TMVbSrvr)
    {
     flag = TRUE;
     setsockopt(0, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

     /* Fire an Ack character up the connection to start the conversation.
        This is necessary because Linux loses anything we send before the
        new process is up and running so TMClient isn't going to talk until
        we go first. The Ack comes from TMSvc on NT style systems.          */

     send(0, "\x06", 1, 0);
    }
   else
    {
     /* Send DO SuppressLocalEcho */
     send(1, "\xff\xfd\x2d", 3, 0);
    }

   n = sizeof(sa);
   getpeername(0, (struct sockaddr *)&sa, &n);
   strcpy(ip_addr, (char *)inet_ntoa(sa.sin_addr));

   n = sizeof(sa);
   getsockname(0, (struct sockaddr *)&sa, &n);
   port_no = ntohs(sa.sin_port);

   /* Create output buffer */

   outbuf = (char *)malloc(OUTBUF_SIZE);
   if (outbuf == NULL)
    {
     printf("Unable to allocate socket output buffer\n");
     return FALSE;   /* Error */
    }
  }

 case_inversion = TRUE;
 set_term(TRUE);

 /* Set up signal handler */

 signal(SIGINT, signal_handler);
 signal(SIGHUP, signal_handler);
 signal(SIGTERM, signal_handler);

#ifdef ASYNCIO
 /* Set up a signal handler to catch SIGIO generated by arrival of
    input data.                                                       */

 signal(SIGIO, io_handler);
 fcntl(0, F_SETOWN, (int) getpid());
 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK|O_ASYNC);
#endif

 return TRUE;
}

/* ====================================================================== */

bool init_console()
{
 struct stat statbuf;

 /* ----------------------- Display ------------------------ */

 tio.dsp.width = 80;
 tio.dsp.lines_per_page = 24;

 tsettermtype("");

 if (connection_type == CN_CONSOLE)
  {
   /* ----------------------- Keyboard ----------------------- */

   /* Fetch the current terminal settings and attempt to set new ones. */

   ttyin = 0;
   if (!tcgetattr(ttyin, &old_tty_settings))
    {
     tty_modes_saved = TRUE;

     /* Construct desired settings */

     new_tty_settings = old_tty_settings;

     new_tty_settings.c_iflag &= ~ISTRIP;     /* 8 bit input */
     new_tty_settings.c_iflag |= IGNPAR;      /* Disable parity */
     new_tty_settings.c_iflag &= ~ICRNL;      /* Do not map CR to NL */
     new_tty_settings.c_iflag &= ~IGNCR;      /* Do not discard CR */
     new_tty_settings.c_iflag &= ~INLCR;       /* Do not map NL to CR */
     new_tty_settings.c_iflag &= ~IXON;       /* Kill X-on/off for output... */
     new_tty_settings.c_iflag &= ~IXOFF;      /* ...and input */

     new_tty_settings.c_cflag &= ~CSIZE;      /* Enable... */
     new_tty_settings.c_cflag |= CS8;         /* ...8 bit operation */

     new_tty_settings.c_lflag &= ~ICANON;     /* No erase/kill processing */
     new_tty_settings.c_lflag |= ISIG;        /* Enable signal processing */
     new_tty_settings.c_lflag &= ~ECHO;       /* Half duplex */
     new_tty_settings.c_lflag &= ~ECHONL;     /* No echo of linefeed */

     new_tty_settings.c_cc[VMIN] = 1;         /* Single character input */
     new_tty_settings.c_cc[VQUIT] = '\0';     /* No quit character */
     new_tty_settings.c_cc[VSUSP] = '\0';     /* No suspend character */
     new_tty_settings.c_cc[VEOF] = '\0';      /* No eof character */

     /* Attempt to set device to this mode */

     tcsetattr(ttyin, TCSANOW, &new_tty_settings);
    }
  }

 case_inversion = TRUE;
 set_term(TRUE);

 fstat(0, &statbuf);
 if (S_ISFIFO(statbuf.st_mode)) piped_input = TRUE;

 /* Set up signal handler  0351 */

 signal(SIGINT, signal_handler);
 signal(SIGHUP, signal_handler);
 signal(SIGTERM, signal_handler);

 return TRUE;
}

/* ======================================================================
   set_term()  -  Set or reset terminal modes                             */

void set_term(trap_break)
   bool trap_break;   /* Treat break char as a break? */
{
 trap_break_char = trap_break;

 if (connection_type == CN_CONSOLE)
  {
   if (trap_break_char) new_tty_settings.c_lflag |= ISIG;
   else new_tty_settings.c_lflag &= ~ISIG;
   set_new_tty_modes();
  }
}

/* ======================================================================
   shut_console()  -  Shutdown console functions                          */

void shut_console()
{
 if (connection_type == CN_CONSOLE) set_old_tty_modes();
}

/* ======================================================================
   set_old_tty_modes()  -  Reset tty to modes it had on entry             */

void set_old_tty_modes()
{
 if (tty_modes_saved) tcsetattr(ttyin, TCSANOW, &old_tty_settings);
}

/* ======================================================================
   set_new_tty_modes()  -  Reset tty to modes required by TM              */

void set_new_tty_modes()
{
 tcsetattr(ttyin, TCSANOW, &new_tty_settings);
}

/* ====================================================================== */

bool write_console(char * p, int bytes)
{
 int n;
 
 while(bytes)
  {
   n = write(1, p, bytes);
   if (n < 0)  /* An error occured */
    {
     if (errno != EAGAIN) return FALSE;
     sched_yield();
    }
   else
    {
     bytes -= n;
     p += n;
    }
  }

 return TRUE;
}

/* ======================================================================
   Low level keyboard handling functions                                  */

bool keyready()
{
 /* If there is type-ahead, we can return immediately */

 if (type_ahead >= 0) return TRUE;

 if (piped_input) return TRUE;

 input_handler_enabled = FALSE;

 /* Check if there is anything pending on stdin. We may have had
    the SIGIO handler disabled when this arrived.                       */

 if (tmpoll(0, 0) > 0)
  {
   /* There is something waiting.  Go get it as a type ahead character */
   type_ahead = keyin(0);
  }
 else
  {
   /* Ok, so there's nothing out in the Linux world. Is there anything
      in our ring buffer?                                              */

   if (ring_in == ring_out)
    {
     input_handler_enabled = TRUE;
     return FALSE;
    }

   type_ahead = ring_buff[ring_out];
   ring_out = (ring_out + 1) % RING_SIZE;
  }

 input_handler_enabled = TRUE;
 return TRUE;
}


short int keyin(timeout)
   int timeout;     /* Milliseconds */
{
 u_char c;
 struct timeval tv;
 int64 t1;
 int64 t2;
 int td;
 int poll_ret;
 bool using_autologout = FALSE;


 /* Disable the signal handler. Once this is done, no input can arrive
    from anywhere else. We can then safely test the ring buffer. Because
    we use a signal handler rather than multi-threading, the two styles
    of input cannot be running simulataneously.                          */

 input_handler_enabled = FALSE;

 if (type_ahead >= 0)    /* 0211 */
  {
   c = type_ahead;
   type_ahead = -1;
   goto exit_keyin;
  }
 else
  {
   if (piped_input)
    {
     if (read(0, &c, 1) <= 0)
      {
       process.status = ER_EOF;
       return -1;
      }

     if (c == 10) c = inewline;
     goto exit_keyin;
    }

   if (autologout) using_autologout = (autologout < timeout) || !timeout;
   if (using_autologout) timeout = autologout;

   if (timeout)
    {
     gettimeofday(&tv, NULL);
     t1 = (((int64)(tv.tv_sec)) * 1000) + (tv.tv_usec / 1000);
    }

   while(ring_in == ring_out)   /* Nothing in ring buffer */
    {
     /* Do our own i/o wait so that we can handle events while we are
        waiting. The paths that return special values all re-enable the
        signal handler. If any input arrives between the call to poll()
        and re-enabling the handler, it will be picked up by the next
        call to keyin() or keyready() as it will still be in the queue. */

     do {
         poll_ret = tmpoll(0, (timeout && timeout < 1000)?timeout:1000);

          /* 0188 Added check for EINTR so that signals do not cause us
             to terminate the process.                                  */

          if ((poll_ret < 0) && (errno != EINTR)) k_exit_cause = K_TERMINATE;

          if ((my_uptr != NULL) && (my_uptr->events)) process_events();

          if (k_exit_cause & K_INTERRUPT)
          {
           /* Force our way out for logout, etc */
           input_handler_enabled = TRUE;
           return 0;
          }

         if (timeout)
          {
           gettimeofday(&tv, NULL);
           t2 = (((int64)(tv.tv_sec)) * 1000) + (tv.tv_usec / 1000);
           td = t2 - t1;
           timeout -= td;
           t1 = t2;
           if (timeout <= 0)
            {
             input_handler_enabled = TRUE;

             if (using_autologout)
              {
               tio_printf("%s\n", sysmsg(2503)); /* Inactivity timer expired - Process logged out */
               Sleep(3000);
               k_exit_cause = K_TERMINATE;
              }

             process.status = ER_TIMEOUT;
             return -1;
            }
          }
        } while(poll_ret <= 0);

     /* There should be something waiting for us.  Because we may choose
        to discard whatever is waiting (NUL, quit key, etc), we must go
        round the loop again to check if there is now anything in the
        ring buffer.                                                      */

     do_input();
    }

   /* Grab the character from the ring buffer */

   c = ring_buff[ring_out];
   ring_out = (ring_out + 1) % RING_SIZE;
  }

exit_keyin:

 /* Re-enable the handler. As above, any input arriving in the final
    moments before it was re-enabled will be picked up later.         */

 input_handler_enabled = TRUE;
 return c;
}

#ifdef ASYNCIO
void io_handler(int sig)
{
 /* Collect the input and re-enable the signal */

 if (input_handler_enabled) do_input();
 signal(SIGIO,io_handler);
}
#endif

private void do_input()
{
 u_char c;
 short int n;
 static bool last_was_cr = TRUE;  /* May need to skip leading NUL/LF */

again:
 while(1)
  {
   if (tmpoll(0, 0) <= 0) break;

   n = (ring_in + 1) % RING_SIZE;
   if (n == ring_out) return;       /* Ring buffer is full */

   if (read(0, &c, 1) <= 0)
    {
     if (errno == EAGAIN) goto again;  /* 0429 io_handler() stole our data */

     /* Lost connection */
     k_exit_cause = K_TERMINATE;  /* 0393 */
     c = 0;    /* 0338 */
//0338     return;
    }

   if (!is_TMVbSrvr)
    {
     if (c == tio.break_char)           /* The break key */
      {
       if (trap_break_char)
        {
         break_key();
         continue;
        }
      }
     else
      {
       if (c == TN_IAC)
        {
         (void)negotiate_telnet_parameter();
         continue;
        }
      }
    }

   if (!telnet_binary_mode_in)
    {
     if (last_was_cr)
      {
       last_was_cr = FALSE;
       if (c == 0) continue;     /* Ignore NUL after CR */
       if (c == 10) continue;    /* Ignore LF after CR */
      }
     last_was_cr = (c == 13);

     if (c == 13) c = inewline;
    }
   else last_was_cr = FALSE;     /* Ready for exit from binary mode */

   if (ChildPipe >= 0)
    {
     if (c == inewline) c = 10;
     tio_display_string(&c, 1, TRUE);
     write(ChildPipe, &c, 1);
    }
   else
    {
     /* Pop this character into the ring buffer */

     ring_buff[ring_in] = c;
     ring_in = (ring_in + 1) % RING_SIZE;
    }
  }
}

/* ======================================================================
   save_screen()  -  Save screen image                                    */

bool save_screen(scrn, x, y, w, h)
   SCREEN_IMAGE * scrn;
   short int x;
   short int y;
   short int w;
   short int h;
{
 char * p;
 char * q;
 static long int image_id = 0;

 if (connection_type == CN_SOCKET)
  {
   scrn->id = image_id++;
   p = tmtgetstr("sreg");
   if (p != NULL)
    {
     q = tparm(p, (int)(scrn->id), (int)x, (int)y, (int)w, (int)h);
     write_socket(q, strlen(q), TRUE);
    }
  }

 return TRUE;
}

/* ====================================================================== */

void restore_screen(scrn, restore_cursor)
   SCREEN_IMAGE * scrn;
   bool restore_cursor;
{
 char * p;
 char * q;

 if (connection_type == CN_SOCKET)
  {
   p = tmtgetstr("rreg");
   if (p != NULL)
    {
     q = tparm(p, (int)(scrn->id), (int)(scrn->x), (int)(scrn->y), (int)restore_cursor);
     write_socket(q, strlen(q), TRUE);
    }
  }
}



/* Interludes to map onto Windows style interfaces */

bool read_socket(str, bytes)
   char * str;
   int bytes;
{
 while(bytes--) *(str++) = keyin(0);
 return 1;
}

char socket_byte()
{
 char c;
 read(0, &c, 1);
 return c;
}

/* ======================================================================
   login_user()  -  Perform checks and login as specified user            */

bool login_user(username, password)
   char * username;
   char * password;
{
 FILE * fu;
 struct passwd * pwd;
 char pw_rec[200+1];
 short int len;
 char * p = NULL;
 char * q;

 if ((fu = fopen(PASSWD_FILE_NAME, "r")) == NULL)
  {
   tio_printf("%s\n", sysmsg(1007));
   return FALSE;
  }

 len = strlen(username);


 while(fgets(pw_rec, sizeof(pw_rec), fu) > 0)
  {
   if ((pw_rec[len] == ':') && (memcmp(pw_rec, username, len) == 0))
    {
     p = pw_rec + len + 1;
     break;
    }
  }
 fclose(fu);

 if (p != NULL)
  {
   if (memcmp(p, "$1$", 3) == 0)    /* MD5 algorithm */
    {
     if ((q = strchr(p, ':')) != NULL) *q = '\0';
     if (strcmp((char *)crypt(password, p), p) == 0)
      {
       if (((pwd = getpwnam(username)) != NULL)
        && (setgid(pwd->pw_gid) == 0) && (setuid(pwd->pw_uid) == 0))
        {
//         set_groups();
         return TRUE;
        }
      }
    }
  }

 return FALSE;
}


/* ======================================================================
   Signal handler                                                         */

void signal_handler(signum)
   int signum;
{
 switch(signum)
  {
   case SIGINT:
#ifdef SVID
      signal(SIGINT, signal_handler);
#endif
      break_key();
      break;

   case SIGHUP:
   case SIGTERM:
      signal(SIGHUP, SIG_IGN);
      signal(SIGTERM, SIG_IGN);
      if (my_uptr != NULL) my_uptr->events |= EVT_TERMINATE;  /* 0393 */
      break;
  }
}

/* ======================================================================
   flush_outbuf()  -  Flush socket output buffer                          */

bool flush_outbuf()
{
 if (outbuf_bytes)
  {
   if (!write_console(outbuf, outbuf_bytes)) return FALSE;
   outbuf_bytes = 0;
  }

 return TRUE;
}

/* ====================================================================== */

int tmpoll(int fd, int timeout)
{
#ifdef DO_NOT_USE_POLL
 fd_set fds;
 struct timeval tv;

 FD_ZERO(&fds);
 FD_SET(fd, &fds);
 tv.tv_sec = timeout / 1000;
 tv.tv_usec = (timeout % 1000) * 1000;
 return select(1, &fds, NULL, NULL, &tv);
#else
 struct pollfd fds[1];

 fds[0].fd = fd;
 fds[0].events = POLLIN;

 return poll(fds, 1, timeout);
#endif
}

/* END-CODE */
